package entities;

import java.io.Serializable;
import java.util.*;
import metadata.SystemMetaData;
import metadata.TableMetaData;
import org.jgap.IChromosome;
import org.jgap.impl.BooleanGene;

/**
 *
 * @author Dragan Sahpaski
 */
public class Table implements ITableVariant, ITable, Iterable<Table>, Serializable {

    protected String name;
    protected long numberOfTuples;
    // Basic Set of Atributes
    protected PrimaryKey primaryKey;
    protected List<Table> fkRelations;
    protected List<Atribute> descriptiveAtributes;

    // Aditional atributes
    protected HashSet<Atribute> materializedAtributes;
    protected HashSet<Atribute> indexedAtributes;

    // Higher granularity
    protected List<Table> parents;
    protected HashSet<Table> allParents;
    // Lower granularity
    protected List<Table> allChildren;
    // The length in bits of the table in the gene
    protected int bitLength;
    // The gene index in the chromozome
    protected int tableIndex;
    protected boolean isTableMaterialized;
    protected HashMap<Atribute, Integer> atributes;
    protected HashMap<Index, Integer> indices;

    public Table(String name, int numberOfTuples, PrimaryKey primaryKey, List<Atribute> descriptiveAtributes) {
        this.name = name;
        this.numberOfTuples = numberOfTuples;
        this.primaryKey = primaryKey;
        this.descriptiveAtributes = descriptiveAtributes;

        this.primaryKey.setTable(this);
        this.descriptiveAtributes = descriptiveAtributes;
        for (Atribute atribute : descriptiveAtributes) {
            atribute.setTable(this);
            atribute.setNumberOfDistinctValues(numberOfTuples);
        }
        this.atributes = new HashMap<Atribute, Integer>();
        this.indices = new HashMap<Index, Integer>();
        this.parents = new ArrayList<Table>();
        this.allParents = new HashSet<Table>();
        allChildren = new ArrayList<Table>();
        this.fkRelations = new ArrayList<Table>();
        this.indexedAtributes = new HashSet<Atribute>();
        this.materializedAtributes = new HashSet<Atribute>();
        this.materializedAtributes.addAll(this.descriptiveAtributes);
        this.materializedAtributes.add(this.primaryKey.getAtribute());
    }

    protected Table() {
    }

    public void calcBitLength() {
        // length of the denormalized atributes
        bitLength = calcDenomalizedAtrsAndIndicesLength();
    }

    public boolean isNullRelation() {
        return this.name.equals("NULL");
    }

    public boolean isParentOf(Relation rel) {
        return allChildren.contains(rel);
    }

    public int dimensionCount() {
        return this.fkRelations.size();
    }

    public long getNumberOfTuples() {
        return this.numberOfTuples;
    }

    public List<Table> getFKRelations() {
        return this.fkRelations;
    }

    public List<Atribute> getDescriptiveAtributes() {
        return descriptiveAtributes;
    }

    public boolean isRootView() {
        return allParents.size() == 0;
    }

    public Iterator<Table> iterator() {
        List<Table> hierarchy = new ArrayList<Table>();
        hierarchy.add(this);
        hierarchy.addAll(allChildren);
        return hierarchy.iterator();
    }

    public boolean isParentOf(Table table) {
        return allParents.contains(table);
    }

    public List<Table> getParents() {
        return parents;
    }

    public String getName() {
        return name;
    }
    /*
     * The bitLength of the gene representing the class of variants-materialized views
     * calculated from this view with its level of aggregation.
     * Variants include denormalized atributes and indices on that atributes.
     */

    public int getBitLength() {
        return bitLength;
    }

    public int getTableIndex() {
        return tableIndex;
    }

    public void setTableIndex(int index) {
        this.tableIndex = index;
    }

    public Iterable<Atribute> getMaterializedAtributes() {
        return materializedAtributes;
    }

    public Iterable<Atribute> getIndexedAtributes() {
        return this.indexedAtributes;
    }

    public boolean isAtributeMaterialized(Atribute atribute) {
        return materializedAtributes.contains(atribute);
    }

    public boolean isAtributeMaterialized(String atributeName) {
        for (Atribute a : this.getMaterializedAtributes()) {
            if (a.getName().equals(atributeName)) {
                return true;
            }
        }
        return false;
    }

    public boolean isAtributeIndexed(Atribute atribute) {
        return indexedAtributes.contains(atribute);
    }

    public boolean isAtributeIndexed(String atributeName) {
        for (Atribute a : this.getIndexedAtributes()) {
            if (a.getName().equals(atributeName)) {
                return true;
            }
        }
        return false;
    }

    public Index getIndex(Atribute a) {
        if (isAtributeIndexed(a)) {
            return a.getIndex();
        } else {
            return null;
        }
    }

    public PrimaryKey getPrimaryKey() {
        return primaryKey;
    }

    public HashSet<Table> getAllParents() {
        return allParents;
    }

    public List<Table> getAllChildren() {
        return allChildren;
    }

    public void setAllChildren(List<Table> allChildren) {
        this.allChildren = allChildren;
    }

    public boolean isTableMaterialized() {
        return isTableMaterialized;
    }

    public void setMaterializedAtributes(HashSet<Atribute> materializedAtributes) {
        this.materializedAtributes = materializedAtributes;
    }

    private int calcDenomalizedAtrsAndIndicesLength() {
        // 0 index of Gene is for: isViewMaterialized or not
        int length = 1;
        for (Table dimRelation : this.getFKRelations()) {
            for (Table table : dimRelation) {
                if (!table.isNullRelation() && !table.equals(dimRelation)) {
                    atributes.put(table.getPrimaryKey().getAtribute(), length++);
                }
                for (Atribute a : table.getDescriptiveAtributes()) {
                    atributes.put(a, length++);
                }
            }
        }

        if (!this.isNullRelation()) {
            indices.put(new Index(this, this.getPrimaryKey().getAtribute()), length);
            length++;
            for (Atribute a : this.getDescriptiveAtributes()) {
                indices.put(new Index(this, a), length);
                length++;
            }
        }
        // Calculate the indices
        for (Table dimRelation : this.fkRelations) {
            for (Table relation : dimRelation) {
                if (!relation.isNullRelation()) {
                    indices.put(new Index(this, relation.getPrimaryKey().getAtribute()), length);
                    length++;
                    for (Atribute a : relation.getDescriptiveAtributes()) {
                        indices.put(new Index(this, a), length);
                        length++;
                    }
                }
            }
        }
        return length;
    }

    /**
     * Computes the TableVariant objects part of this Table.
     * @see #getMaterializedAtributes()
     * @param chromosome The chromosome from witch the TableVariant objects will be computed
     */
    @Override
    public void calculateTableVariant(
            IChromosome chromosome) {
        materializedAtributes.clear();
        indexedAtributes.clear();
        // The tables's dimension relation's PK's are always materialized
        // That is why they are not present in the gene itself
        for (Table dimTable : this.fkRelations) {
            materializedAtributes.add(dimTable.getPrimaryKey().getAtribute());
        }
        // Descriptive atributes are always materialized
        // Measures are not always materialized
        for (Atribute descAtribute : this.descriptiveAtributes) {
            if (!descAtribute.isIsMeasure()) {
                materializedAtributes.add(descAtribute);
            }
        }
        this.isTableMaterialized = ((BooleanGene) chromosome.getGene(this.getTableIndex())).booleanValue();
        for (Atribute a : atributes.keySet()) {
            boolean isAtrMat = ((BooleanGene) chromosome.getGene(this.getTableIndex() + atributes.get(a).intValue())).booleanValue();
            if (isAtrMat) {
                materializedAtributes.add(a);
            }
        }

        for (Index index : indices.keySet()) {
            boolean isIndexMat = ((BooleanGene) chromosome.getGene(this.getTableIndex() + indices.get(index).intValue())).booleanValue();
            if (isIndexMat) {
                indexedAtributes.add(index.getAtribute());
            }
        }
    }

    public TableMetaData getTableMetaData(SystemMetaData systemMetaData) {
        int tupleSizeInBytes = 0;
        for (Atribute a : this.materializedAtributes) {
            tupleSizeInBytes += a.getSizeInBytes();
        }
        int diskPageSizeInBytes = systemMetaData.getDiskPageSizeInBytes();
        long numberOfDiskPages = (long) ((double) this.numberOfTuples * (double) tupleSizeInBytes / (double) diskPageSizeInBytes);
        if (numberOfDiskPages == 0) {
            numberOfDiskPages = 1;
        }
        return new TableMetaData(this.getName(), this.getNumberOfTuples(), tupleSizeInBytes, numberOfDiskPages);
    }

    public String getBasicDesc() {
        String str = this.getName() + " <";
        for (Table fk : this.fkRelations) {
            str += fk.getName() + " ";
        }
        str += this.descriptiveAtributes.size() > 0 ? ": " : "";
        for (Atribute a : this.getDescriptiveAtributes()) {
            str += a.getName() + " ";
        }
        if (!this.isNullRelation()) {
            str = str.substring(0, str.length() - 1);
        }
        str += ">";
        return str;
    }

    @Override
    public String toString() {
        String str = this.getName() + " <";
        for (Table fk : this.fkRelations) {
            str += fk.getName() + " ";
        }
        str += this.descriptiveAtributes.size() > 0 ? ": " : "";
        for (Atribute a : this.getDescriptiveAtributes()) {
            str += a.getName() + " ";
        }
        if (!this.isNullRelation()) {
            str = str.substring(0, str.length() - 1);
        }
        str += ">";
        str += " pos:" + this.getTableIndex();
        str += " bits:" + this.bitLength;
        str += "\n\tAA: ";
        HashMap<Integer, Atribute> atributes1 = new HashMap<Integer, Atribute>();
        for (Atribute a : atributes.keySet()) {
            atributes1.put(atributes.get(a), a);
        }
        Object[] atributeIndexes = atributes1.keySet().toArray();
        Arrays.sort(atributeIndexes);
        for (Object k : atributeIndexes) {
            str += k + ":" + atributes1.get(k) + " ";
        }

        str += "\n\tMatAtributes: ";
        for (Atribute a : this.materializedAtributes) {
            str += a.getName() + " ";
        }

        str += "\n\tIndices: ";
        Object[] indexes = indices.values().toArray();
        Arrays.sort(indexes);
        for (Object k : indexes) {
            for (Index index : indices.keySet()) {
                if (indices.get(index).equals(k)) {
                    str += k + ":" + index.getAtribute() + " ";
                }
            }
        }
        str += "\n\tIndexedAtributes: ";
        for (Atribute a : this.indexedAtributes) {
            str += a.getName() + " ";
        }

        str += "\n\tParents: ";
        str += this.getParents().size() == 0 ? "none" : "";
        for (Table table : this.getParents()) {
            str += table.getName() + " ";
        }
        str += "\n\tAllParents:";
        for (Table parent : this.getAllParents()) {
            str += " <";
            for (Table t : parent.getFKRelations()) {
                str += t.getName() + " ";
            }
            str += " rows:";
            str += parent.getNumberOfTuples() + ">";
        }
        str += "\n\tChildren: <";
        for (Table child : this.getFKRelations()) {
            str += child.getName() + " ";
        }
        if (this.getFKRelations().size() > 0) {
            str = str.substring(0, str.length() - 1);
        }
        str += ">";
        str += "\n\tAllChildren: ";
        for (Table child : this.getAllChildren()) {
            str += child.getName() + " ";
        }
        str += "\n\tDimension Subtree: ";
        for (Table table : this) {
            str += table.getName() + " ";
        }
        return str;
    }
}
